# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: inkyu

import ujson as json
import functools

from coin.base.book.types import TradeSide, create_market_trade_event
from coin.exchange.base.ws.handler import WebsocketHandler
from coin.exchange.bitfinex_v2.book_builder import BookBuilder
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct


@functools.lru_cache(maxsize=128)
def get_product_from_symbol(symbol):
  return BitfinexProduct.FromStrNativeProduct(symbol)


class BitfinexV2Handler(WebsocketHandler):
  def _handle_subscribed(self, message: dict, topic_data):
    assert message['event'] == 'subscribed'
    channel = message['channel']
    channel_id = message['chanId']
    symbol = message['symbol']
    topic_id = topic_data.topic_id
    if channel == 'trades':
      if not self.is_subscribed('trade', symbol):
        return
      for (old_topic_id, old_channel_id), old_symbol in list(self._trades.items()):
        if old_topic_id == topic_id and old_symbol == symbol:
          del self._trades[(old_topic_id, old_channel_id)]
      self._trades[(topic_id, channel_id)] = symbol
    elif channel == 'book':
      if not self.is_subscribed('book', symbol):
        return
      for (old_topic_id, old_channel_id), old_book in list(self._books.items()):
        if old_topic_id == topic_id and old_book.symbol == symbol:
          del self._books[(old_topic_id, old_channel_id)]
      self._books[(topic_id,
                   channel_id)] = BookBuilder(symbol,
                                              book_builder_observer=self._book_builder_observer)
    else:
      raise ValueError(channel)

  def on_ws_trade(self, record, queue_data, topic_data):
    data = record.data
    if not isinstance(data, dict) and not isinstance(data, list):
      data = json.loads(data.tobytes())

    if isinstance(data, dict):
      # TODO(inkyu): Move to somewhere
      self._handle_subscribed(data, topic_data)
      return

    if not isinstance(data, list):
      return

    if len(data) == 3:
      channel_id, action, message = data
      if action == 'te':  # Trade execution
        if (topic_data.topic_id, channel_id) not in self._trades:
          return
        # trade_id = message[0]
        symbol = self._trades[(topic_data.topic_id, channel_id)]

        # TODO(inkyu): Add exchange_timestamp, message[1]
        timestamp = record.timestamp

        qty_signed = float(message[2])
        price = float(message[3])
        if qty_signed > 0:
          trade_side = TradeSide.TRADE_BUY_SIDE
          qty = qty_signed
        else:
          trade_side = TradeSide.TRADE_SELL_SIDE
          qty = -qty_signed
        trade = create_market_trade_event(timestamp,
                                          symbol,
                                          price,
                                          qty,
                                          trade_side,
                                          topic_id=record.topic_id)
        product = get_product_from_symbol(symbol)
        if self._feed_checker:
          self._feed_checker.on_trade(product=product,
                                      trade_event=trade,
                                      timestamp=record.timestamp)
        self.publish_event('trade', symbol, trade)
        return
      elif action == 'tu':  # Trade update, let's ignore for now.
        # https://bitcoin.stackexchange.com/questions/60390/trade-execution-v-s-trade-execution-update/60391
        return
      else:
        raise ValueError(action)

    channel_id, message = data
    if (topic_data.topic_id, channel_id) in self._trades:
      # Trade snapshot
      if isinstance(message, list):
        # TODO(inkyu): Implement this snapshot
        return
      else:
        assert message == 'hb'
        return
      raise ValueError('Unreachable code')

  def on_ws_book(self, record, queue_data, topic_data):
    data = record.data
    if not isinstance(data, dict) and not isinstance(data, list):
      data = json.loads(data.tobytes())

    if isinstance(data, dict):
      # TODO(inkyu): Move to somewhere
      self._handle_subscribed(data, topic_data)
      return

    elif isinstance(data, list):
      assert len(data) == 2
      channel_id, message = data
      if (topic_data.topic_id, channel_id) not in self._books:
        return
      book = self._books[(topic_data.topic_id, channel_id)]
      if isinstance(message, list):
        if message and isinstance(message[0], list):
          book.snapshot(message)
        else:
          if not book.update(message):
            # Return if the update is not successful
            return
      else:
        assert message == 'hb', 'Invalid message'
      book.update_timestamp(record.timestamp)
      book.update_topic_id(record.topic_id)
      if self._feed_checker:
        self._feed_checker.on_book(product=book.product, book=book, timestamp=record.timestamp)
      self.publish_event('book', book.symbol, book)
    else:
      raise ValueError('Invalid data type: %s' % data)

  # Deprecated
  on_ws_trades = on_ws_trade
